package com.cyl.wechat;



import cn.hutool.crypto.PemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Base64Utils;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

public class WechatPayUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    private static final Logger log = LoggerFactory.getLogger(WechatPayUtil.class);

    /**
     * 验证微信支付回调签名
     */
    public static boolean isWechatPayNotifyValid(String xmlData) {
        try {
            Map<String, String> map = xmlToMap(xmlData);
            String sign = map.get("sign");
            if (sign == null) {
                return false;
            }
            map.remove("sign");
            String generatedSign = generateSignature(map, WechatPayData.apiV3key);
            return sign.equals(generatedSign);
        } catch (Exception e) {
            log.error("验证微信支付回调签名异常", e);
            return false;
        }
    }

    /**
     * XML转Map
     */
    public static Map<String, String> xmlToMap(String xmlStr) throws Exception {
        Map<String, String> resultMap = new HashMap<>();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputStream is = new ByteArrayInputStream(xmlStr.getBytes("UTF-8"));
        Document document = builder.parse(is);
        NodeList nodeList = document.getDocumentElement().getChildNodes();

        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                resultMap.put(node.getNodeName(), node.getTextContent());
            }
        }
        return resultMap;
    }

    /**
     * Map转XML
     */
    public static String mapToXml(Map<String, String> params) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.newDocument();
        Element root = document.createElement("xml");
        document.appendChild(root);

        for (Map.Entry<String, String> entry : params.entrySet()) {
            Element element = document.createElement(entry.getKey());
            element.setTextContent(entry.getValue());
            root.appendChild(element);
        }

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(document), new StreamResult(writer));
        return writer.getBuffer().toString();
    }

    /**
     * 生成签名
     */
    public static String generateSignature(Map<String, String> params, String apiKey) {
        try {
            StringBuilder sb = new StringBuilder();
            // 按照key的ASCII码从小到大排序
            List<String> keys = new ArrayList<>(params.keySet());
            Collections.sort(keys);

            for (String key : keys) {
                String value = params.get(key);
                if (value != null && !value.isEmpty() && !"sign".equals(key) && !"key".equals(key)) {
                    sb.append(key).append("=").append(value).append("&");
                }
            }
            sb.append("key=").append(apiKey);

            // MD5签名
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(sb.toString().getBytes("UTF-8"));
            StringBuilder result = new StringBuilder();
            for (byte item : array) {
                result.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
            return result.toString().toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("生成签名失败", e);
        }
    }

    public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
        //replace 根据实际情况，不一定都需要
        String replace = privateKey.replace("\\n", "\n");
        InputStream certStream = Files.newInputStream(Paths.get(privateKey));
        PrivateKey merchantPrivateKey = PemUtil.readPemPrivateKey(certStream);
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳，单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳，单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

}
